package kg.aibek.dc;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

public class ConditionalAdaptiveDecoder implements ArithmeticDecoder {
	Map<Character, AdaptiveModel> contextMap = new HashMap<Character, AdaptiveModel>();

	private int low = 0, high = (int) Math.pow(2, AdaptiveModel.BIT_LENGTH) - 1;
	private int value = 0;
	private String decodedStr = "";
	private int quarter = (int) Math.pow(2, AdaptiveModel.BIT_LENGTH - 2);

	private Map<Integer, Integer> rateMap = new HashMap<Integer, Integer>();

	public void decode(String filePath) throws IOException {
		FileInputStream fis = new FileInputStream(filePath);

		Reader reader = new InputStreamReader(fis);

		int r = -1;
		int counter = 0;
		boolean read = false;

		Character prev = null;

		while (read || (r = reader.read()) != -1) {
			if (r >= 58 || r <= 47) {
				break;
			}

			if (counter < AdaptiveModel.BIT_LENGTH) {
				counter++;
				value = 2 * value + (new Integer((char) r + ""));
				continue;
			}

			if (contextMap.get(prev) == null) {
				contextMap.put(prev, new AdaptiveModel());
			}
			AdaptiveModel model = contextMap.get(prev);

			int[] cumCount = model.getCumulativeFrequency();
			int totalCount = model.getTotalFrequency();
			int range = high - low + 1;

			int symbol = 0;
			while (value > low + (int) (range * cumCount[symbol] / (double) totalCount - 1)) {
				symbol++;
			}

			if (symbol == 3) {
				break;
			}

			int newLow = low + (int) (range * cumCount[symbol - 1] / (double) totalCount);
			int newHigh = low + (int) (range * cumCount[symbol] / (double) totalCount) - 1;
			low = newLow;
			high = newHigh;

			prev = (char) symbol;
			model.addContext((char) symbol);

			decodedStr += (char) symbol;

			read = false;
			while (true) {
				int msbLow = Utility.getBitAtPosition(low, 0);
				int msbHigh = Utility.getBitAtPosition(high, 0);

				if (msbLow == msbHigh) {
					low = Utility.shiftMSB(low, 0);
					high = Utility.shiftMSB(high, 1);
					value = Utility.shiftMSB(value, new Integer((char) r + ""));

					read = true;
					if ((r = reader.read()) == -1 || r > 57 || r < 48) {
						break;
					}
				} else {
					if (Utility.getBitAtPosition(low, 1) == 1
							&& Utility.getBitAtPosition(high, 1) == 0) {
						low -= quarter;
						high -= quarter;
						value -= quarter;

						low = Utility.shiftMSB(low, 0);
						high = Utility.shiftMSB(high, 1);
						value = Utility.shiftMSB(value, new Integer((char) r + ""));

						read = true;
						if ((r = reader.read()) == -1 || r > 57 || r < 48) {
							break;
						}

						// low = Utility.complementMSB(low);
						// high = Utility.complementMSB(high);
					} else {
						break;
					}
				}
			}
		}
	}

	public String getDecodedStr() {
		return decodedStr;
	}
}
